'''


'''
from tkinter import *
import socket
import time
from tkinter import messagebox
from tkinter.scrolledtext import ScrolledText
from threading import Thread
from multiprocessing import Process
import multiprocessing as mp
import struct
import json
import re



class Client:  # 创建客户端的模板类192.168.146.1
    def __init__(self):
        print("初始化tcp多人聊天室客户端")
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)#tcp心跳维持
        self.socket.connect(('xx.xx.xx.xx', 9999))#
        self.buffer_size = 1024

    def send(self, send_message):
        header_dic = {
            "send_data1" : len(send_message.encode('utf-8')),
        }
        header_json = json.dumps(header_dic)
        print('header_json:',header_json)
        header_bytes = header_json.encode('utf-8')
        header_size = len(header_bytes)
        print('header_size:',header_size)
        print('发送内容:',send_message)
        #用struct将报头长度转成固定的4字节长度
        self.socket.send(struct.pack('i', header_size))
        #发送报头
        self.socket.send(header_bytes)
        #发送数据
        self.socket.send(send_message.encode('utf-8'))

    def receive(self):
        header_size = struct.unpack('i', self.socket.recv(4))[0]
        # 接收报文
        print('header_size:',header_size)
        header_bytes = self.socket.recv(header_size)
        # 解析报文
        header_json = header_bytes.decode('utf-8')
        print('header_json:',header_json)
        header_dic = json.loads(header_json)
        # 获取真实数据的长度
        data1_len = header_dic['send_data1']
        # 获取数据
        rec_data1 = self.socket.recv(data1_len)
        print('收到内容：',rec_data1.decode('utf-8'))
        return rec_data1.decode('utf-8')

    def send_login_info(self, username, password):  # 发送登录用户的用户名和密码给服务器验证，并return验证结果
        # 告诉服务器本次请求的类型，“1” 是验证登录
        username_psw = username + "#!#!" + password
        # 获取服务器的返回值，"1"代表通过，“0”代表不通过，再放回True or False
        self.send(send_message="1"+username_psw)
        check_result =self.receive()
        return check_result

    def send_register_info(self, username, password, confirm):  # 发送用户注册的用户名和密码给服务器，并返回注册结果
        # 判断两次输入的密码是否一致
        if not password == confirm:
            return "密码不一致，请重新输入！"
        # 告诉服务器本次请求类型，“2” 是注册用户
        # 将用户名和密码按一定规律组装后发送给服务器
        username_psw = username + "#!#!" + password
        self.send(send_message="2"+username_psw)
        # 获取服务器返回的结果
        check_result = self.receive()
        return check_result

    def send_msg(self, content):  # 告诉服务器本次请求类型，“3” 是发送消息,param content: 待发送的内容
        self.send(send_message="3"+content)

    def close(self):  # 关闭客户端与服务器连接的套接字
        self.socket.close()


class ManTk(object):
    def __init__(self):
        self.frame = Tk()

    def set_panel_position(self, width, height):  # 设置登陆界面在屏幕中的位置和大小
        screen_width = self.frame.winfo_screenwidth()  # 获取屏幕宽度
        screen_height = self.frame.winfo_screenheight()  # 获取屏幕高度
        gm_str = "%dx%d+%d+%d" % (width, height, (screen_width - width) / 2, (screen_height - 1.2 * height) / 2)
        self.frame.geometry(gm_str)

    def set_title(self, title):
        self.frame.title(title)

    def close(self):  # 实现对界面的关闭
        if self.frame is None:
            print("未显示界面")
        else:
            self.frame.destroy()


class LoginPanel(ManTk):  # 登录界面模板类，只需另外传入其他对象的方法

    def __init__(self):
        super(LoginPanel, self).__init__()
        self.username = None
        self.password = None

    def config_for_reg_panel(self):  # 给登陆界面设置其他配置
        self.frame.configure(background="lightblue")
        self.frame.resizable(width=False, height=False)  # 设置界面大小可调

    def set_form(self):  # 创建框架，添加用户名、密码标签，并设置字体、背景色、前景色并用grid布局，设置输入框，储存用户输入的用户名和密码
        form_frame = Frame(self.frame,background="lightblue")
        form_frame.pack(fill=X, padx=50, pady=(30, 20))
        Label(form_frame, text="username：", font=("Microsoft Yahei", 15), fg="black",background="lightblue").grid(row=0, column=3, pady=15)
        Label(form_frame, text="password：", font=("Microsoft Yahei", 15), fg="black",background="lightblue").grid(row=1, column=3, pady=15)
        self.username = StringVar()
        self.password = StringVar()
        Entry(form_frame, textvariable=self.username, background="lightblue", width=25).grid(row=0, column=4, ipady=1)
        Entry(form_frame, textvariable=self.password, show="*", background="lightblue", width=25).grid(row=1, column=4, ipady=1)

    def set_btn(self):  # 放置注册和登陆按钮
        btn_frame = Frame(self.frame,background="lightblue")
        btn_reg = Button(btn_frame, text="Register", fg="black",background="lightblue", width=10,
                         font=('Microsoft Yahei', 15), command=self.reg_func)
        btn_reg.pack(side=LEFT, ipady=3)
        btn_login = Button(btn_frame, text="Login", fg="black",background="lightblue", width=10,
                           font=('Microsoft Yahei', 15), command=self.login_func)
        btn_login.pack(side=RIGHT, ipady=3)
        btn_frame.pack(fill=X, padx=60, pady=0)

    def show(self):  # 调用实例方法给登录界面做整体布局
        self.set_panel_position(width=400, height=250)
        self.config_for_reg_panel()
        self.set_title(title='let\'s chat')
        self.set_form()
        self.set_btn()
        self.frame.mainloop()

    def get_input(self):  # 获取用户输入的用户名和密码
        return self.username.get(), self.password.get()

    def login_func(self):  # 封装到登陆界面中的登录按钮的功能。
        username, password = self.get_input()
        client = Client()
        check_result = client.send_login_info(username, password)
        print(check_result)
        if check_result == "登录成功！":
            messagebox.showinfo(title="Success", message="登陆成功！")
            self.close()
            recv_data = client.receive()
            print(recv_data)
            ret = re.match(r"(#![\w]{7}#!)([\s\S]+)", recv_data)
            print("recieved type: ")
            option = ret.group(1)
            print(option)
            if option == "#!friends#!":
                print("获取在线用户列表数据")
                # 将一次性获取得到的用户名以 “&!”为标记分隔成一个列表
                print(ret.group(2))
                friend_tumple = ret.group(2).strip().split("&!")
                print(friend_tumple)
                friend_tumple.remove('')  # 去除列表中的空字符串
                print(type(friend_tumple))

            main_panel = MainPanel(username, client, friend_tumple)
            Thread(target=main_panel.handle_msg).start()
            #mp.process(target = aa,args=(main_panel,)).start()
            #ss.daemon = True  #设置该进程为守护进程
            #main_panel.start()
            print("线程创建成功...")
            main_panel.show()
        elif check_result == "密码输入有误，请重新输入！":
            messagebox.showerror(title="Error", message="密码输入有误，请重新输入！")
        elif check_result == "不存在该用户，请先注册！":
            messagebox.showerror(title="Error", message="不存在该用户，请先注册！")

    def reg_func(self):  # 封装到登录界面的注册按钮中，实现从登录界面跳转到注册界面
        self.close()
        reg_panel = RegPanel()
        reg_panel.show()


class RegPanel(ManTk):  # 注册界面模板类，只需另外传入其他对象的方法
    def __init__(self):
        super(RegPanel, self).__init__()
        self.username = None
        self.password = None
        self.confirm = None

    def config_for_reg_panel(self):  # 给注册界面设置其他配置
        # self.frame.configure(background="lightblue")
        # 设置窗口关闭按钮时，调用方法，用于退出时关闭socket连接
        self.frame.protocol("WM_DELETE_WINDOW", self.close_callback)
        # 界面可调整大小
        self.frame.resizable(width=False, height=False)

    def set_form(self):  # 设置页面布局
        # 放置注册表单
        form_frame = Frame(self.frame)
        form_frame.pack(fill=X, padx=50, pady=(50,10))
        Label(form_frame, text="username：", font=("Microsoft Yahei", 15), fg="black").grid(row=0, column=1, pady=10)
        Label(form_frame, text="password：", font=("Microsoft Yahei", 15), fg="black").grid(row=1, column=1, pady=10)
        Label(form_frame, text=" confirm：", font=("Microsoft Yahei", 15), fg="black").grid(row=2, column=1, pady=10)
        # 设置变量，存储用户名和密码
        self.username = StringVar()
        self.password = StringVar()
        self.confirm = StringVar()
        # 设置输入框
        Entry(form_frame, textvariable=self.username, bg="#e3e3e3", width=25).grid(row=0, column=2, ipady=1)
        Entry(form_frame, textvariable=self.password, show="*", bg="#e3e3e3", width=25) \
            .grid(row=1, column=2, ipady=1)
        Entry(form_frame, textvariable=self.confirm, show="*", bg="#e3e3e3", width=25) \
            .grid(row=2, column=2, ipady=1)

    def set_btn(self):  # 放置取消和注册按钮
        btn_frame = Frame(self.frame)
        btn_quit = Button(btn_frame, text="Cancel", fg="black", width=10,
                          font=('Microsoft Yahei', 15), command=self.quit_func)
        btn_quit.pack(side=LEFT, ipady=3)
        btn_reg = Button(btn_frame, text="Register", fg="black", width=10,
                         font=('Microsoft Yahei', 15), command=self.reg_func)
        btn_reg.pack(side=RIGHT, ipady=3)
        btn_frame.pack(fill=X, padx=50, pady=(10, 10))

    def show(self):  # 注册界面布局
        self.set_panel_position(width=400, height=300)
        self.config_for_reg_panel()
        self.set_title('Register')
        self.set_form()
        self.set_btn()
        self.frame.mainloop()  # 启动注册界面，让其显示

    def get_input(self):  # 获取输入的用户名、密码、确认密码
        return self.username.get(), self.password.get(), self.confirm.get()

    def quit_func(self):  # 封装到注册界面中的取消按钮中，实现从注册界面跳转到登陆界面
        self.close()
        login_pane = LoginPanel()
        login_pane.show()

    def reg_func(self):  # 封装到注册界面的注册按钮中
        username, password, confirm = self.get_input()
        client = Client()
        ret = client.send_register_info(username, password, confirm)
        print(ret)
        if ret == "密码不一致，请重新输入！":
            messagebox.showerror(title="Error", message="密码不一致，请重新输入！")
        else:
            if ret == "抱歉，用户名已存在！":
                messagebox.showerror(title="Error", message="抱歉，用户名已存在！")
            elif ret == "注册成功！":
                # 注册成功后提示，然后跳回登录界面
                messagebox.showinfo(title="Success", message="注册成功！")
                self.close()
                login_pane = LoginPanel()
                login_pane.show()
            else:
                messagebox.showerror(title="Error", message="发生未知错误！")

    def close_callback(self):  # 实现从注册页面到登陆页面
        self.close()
        login_pane = LoginPanel()
        login_pane.show()


class MainPanel(ManTk,Process):  # 主界面模板类
    # 初始化实例属性，用户名、在线列表、消息框、消息输入框等
    def __init__(self, username, client, friend_tumple):
        #Process.__init__(self)
        super(MainPanel, self).__init__()
        print("初始化主界面")
        self.frame_button=None
        self.username = username
        self.client = client
        self.friend_tumple = friend_tumple #朋友元组
        self.online_list_box = None     #在线用户列表框
        self.msg_box = None             #聊天框
        self.input_box = None           #输入框
        self.text = dict()              # 存放为每个朋友创建text文本控件
        self.current_friend =None   #当前聊天对象
        self.fr_id = None          #查询的所添加好友id

    def config_for_mian_panel(self):  # 设置主界面
        self.frame.minsize(790, 600)  # 设置最小尺寸
        self.frame.protocol("WM_DELETE_WINDOW", self.close_callback)  # 设置窗口关闭按钮时，调用方法，用于退出时关闭socket连接
        self.frame.resizable(width=False, height=False)

    def set_online_list(self):  # 设置在线用户列表
        def change(event):
            position = event.widget  # 获取事件位置信息
            self.current_friend = position.get(position.curselection())  # w.curselection()获取列项索引，w.get()获取索引内容
            print('current_friend:',self.current_friend)
            print(self.text)
            print('执行结果：',self.text[self.current_friend].grid())
            for val in self.text.keys():
                self.text[val].grid_remove()
            self.text[self.current_friend].grid()
            print('click change()')

        online_list_var = StringVar()   # 设置变量，储存在线列表中的值
        self.online_list_box = Listbox(self.frame, width = 19,selectmode=SINGLE, listvariable=online_list_var, fg="black",bg='lightgrey',
                                       font=("Microsoft Yahei", 14), selectbackground='#00BFFF', highlightcolor="white")
        self.online_list_box.grid(row=0, column=0,rowspan = 3,padx=(10,0), sticky=N + S,)
        list_sr_bar = Scrollbar(self.frame, troughcolor='black')  # 给在线列表设置滚动栏
        list_sr_bar.grid(row=0, column=0,rowspan = 3, sticky=N + S + E,)
        # 设置滚动效果命令，即下拉滚动条和鼠标滚动
        list_sr_bar['command'] = self.online_list_box.yview
        self.online_list_box['yscrollcommand'] = list_sr_bar.set
        self.online_list_box.bind("<<ListboxSelect>>", change)
        self.online_list_box.select_set(0)

    def set_msg_box(self):  # 设置聊天消息显示框
        # #获取所有朋友列表，为每个朋友创建一个text
        if self.friend_tumple:
            for username in self.friend_tumple:
                print('self.friend_tumple.index(username):', self.friend_tumple.index(username))
                self.text[username] = ScrolledText(self.frame, bg='#9BCD9B', height=31, width=67, font=('宋体', '12',))
                self.text[username].grid(row=0, column=1, padx=(3, 0), ipadx=3, sticky=W + N + S, columnspan=20)
                self.text[username].tag_configure('blue', foreground='blue')
                self.text[username].tag_configure('black', foreground='black')
                self.text[username].insert(END, '开始与 ' + username + ' 聊天!\n', 'blue')
                self.text[username].config(state=DISABLED)
                self.text[username].grid_remove()
            self.text[self.friend_tumple[0]].grid()
            print(self.text)
        else:
            self.text[0] = ScrolledText(self.frame, bg='#9BCD9B', height=31, width=67, font=('宋体', '12',))
            self.text[0].grid(row=0, column=1, padx=(3, 0), ipadx=3, sticky=W + N + S, columnspan=20)
            self.text[0].insert(END, '您还没添加朋友！请添加朋友\n', 'blue')
            self.text[0].config(state=DISABLED)

    def set_input_box(self):  # 设置聊天输入窗口

        self.input_box =ScrolledText(self.frame, height=2, width = 43,font=('宋体','15',),bg='lightgrey',highlightcolor="white", highlightbackground="#444444",
                      highlightthickness=3)
        self.input_box.see(END)
        self.input_box.grid(row=2, column=1,columnspan = 19,ipadx=20,sticky=W)# sticky控件紧靠所在单元格的某一边角。

    def add_friends(self):
        def butt_fun():
            self.fr_id = entry_1.get()
            self.client.send(send_message="6" + self.fr_id)
            frame.destroy()

        frame = Tk()
        frame.title('添加好友')
        frame.geometry('300x200')
        frame.resizable(width=False, height=False)
        Label(frame, text='请输入添加好友ID：').pack(pady=(25, 10))
        id = StringVar()
        entry_1 = Entry(frame, textvariable=id, background="lightblue", font=("宋体", 12), justify='center', width=20)
        entry_1.pack(ipady=5, pady=(10, 20))
        Button(frame, text="添加", command=butt_fun, height=1, width=8, font=("宋体", 12), bg="yellow").pack()
        frame.mainloop()

    def set_btn(self):  # 设置发送按钮
        def personal_information_func():
            self.client.send(send_message="5" + self.username)

        frame = Frame(self.frame, bg='lightgrey').grid(row=1, column=1, columnspan=20, sticky=N + S + W + E)#按钮灰色背景
        button_1=Button(self.frame, text="发送",command=self.send_func,height= 1, font=('宋体','15',), fg="black")
        button_1.grid(row=2, column=20,pady=10,sticky=W)
        button_2 = Button(frame, text="个人信息",command=personal_information_func,height = 1,width = 8, font=("宋体", 12), bg="yellow")
        button_2.grid(row=1,column=1,ipadx=5,ipady = 1,pady=5,sticky=W)
        button_3 = Button(frame, text="添加好友",command=self.add_friends,height = 1,width = 8, font=("宋体", 12), bg="red")
        button_3.grid(row=1,column=2,ipadx=5,ipady = 1,pady=5,sticky=W)

    def set_friend_list(self):
        if self.friend_tumple:   #如果self.friend_tumple不为空
            for username in self.friend_tumple:
                self.online_list_box.insert(END, username)#将用户名刷新到朋友列表


    def show(self):  # 主界面布局
        print('主界面布局:')
        self.set_panel_position(width=790, height=600)
        self.config_for_mian_panel()
        self.frame.title("Let\'s chat")
        self.set_msg_box()
        self.set_online_list()
        self.set_friend_list()     #初始化朋友列表
        self.set_input_box()
        self.set_btn()
        self.frame.mainloop()  # 启动

    def handle_msg(self):  # 处理关于在线用户列表和消息框中内容的信息
        while True:
            try:
                recv_data = self.client.receive()
                if recv_data:
                    ret = re.search(r"(#![\w]{7}#!)([\s\S]+)", recv_data)
                    if ret is not None:
                        option = ret.group(1)
                        group_2 = ret.group(2)
                        if option == "#!onlines#!":
                            print("获取在线用户列表数据")
                            # 将一次性获取得到的用户名以 “#!”为标记分隔成一个列表
                            online_usernames = group_2.split("#!")
                            online_usernames.remove("")  # 去除列表中的空字符串
                            print(online_usernames)
                            self.update_online_list(online_usernames)
                        elif option == "#!message#!":  # 正则区分用户名和消息内容
                            print("获取新消息")
                            #信息格式：from_username#!#!to_username#!#!message
                            username_content = group_2
                            ret = re.match(r"(.*)#!#!(.*)#!#!([\s\S]*)", username_content)
                            if ret is not None:
                                from_username = ret.group(1)
                                to_u = ret.group(2)
                                content = ret.group(3)
                                self.set_msg_show_format(from_username, content)
                        elif option == "#!Personl#!":
                            mess = '用户id：'+str(group_2)+'\n 用户名：'+self.username
                            messagebox.showinfo(title="个人信息", message=mess)
                        elif option == "#!Usernam#!":
                            print('#username')
                            mess = '用户id：'+str(self.fr_id)+'\n 用户名：'+str(group_2)+'\n是否添加该用户为好友？'
                            yesno = messagebox.askyesno(title="添加好友", message=mess)
                            print(yesno)
                            if yesno is True:
                                ##from_user#!to_user#!ask_add_friend
                                print('发送标识4')
                                self.client.send(send_message="4" + self.username+"#!#!"+str(group_2))
                        elif option == "#!Askaddf#!":
                            ret = re.match(r"(.*)#!#!(.*)", group_2)
                            if ret is not None:
                                from_username = ret.group(1)
                                mess = '用户：' + from_username+' 请求添加您为好友'+'\n是否同意添加该用户为好友？'
                                yesno = messagebox.askyesno(title="处理添加好友请求", message=mess)
                                if yesno is True:
                                    self.client.send(send_message="7" + self.username + "#!#!" + from_username + "#!#!agree_add_frend")
                        elif option == "#!warring#!":
                            messagebox.showerror(title="error", message=str(group_2))
                        elif option == "#!Agreeee#!":
                            messagebox.showinfo(title='添加好友成功',message=str(group_2))

                    else:
                        print('error:匹配对象为None')
            except Exception as ret:
                    print("接受服务器消息出错，消息接受子线程结束。" + str(ret))
                    break

    def update_online_list(self, online_usernames):  # 刷新在线列表 -- 一遍又一遍地清空，再回填到列表中
        print("正在更新列表...")
        print('在线用户：',online_usernames)
        print('朋友元组：',self.friend_tumple)
        if self.friend_tumple:
            for friend in self.friend_tumple:
                self.online_list_box.itemconfig(self.friend_tumple.index(friend), background='white')
        for username in online_usernames:
            if username in self.friend_tumple :
                #将在线的用户框背景设为绿色
                self.online_list_box.itemconfig(self.friend_tumple.index(username), background='blue')
            else:#处理刚添加的朋友信息
                self.friend_tumple.append(username)
                self.text[username] = ScrolledText(self.frame, bg='#9BCD9B', height=31, width=67,
                                                   font=('宋体', '12',))
                self.text[username].grid(row=0, column=1, padx=(3, 0), ipadx=3, sticky=W + N + S, columnspan=20)
                self.text[username].tag_configure('blue', foreground='blue')
                self.text[username].tag_configure('black', foreground='black')
                self.text[username].insert(END, '开始与 ' + username + ' 聊天!\n', 'blue')
                self.text[username].config(state=DISABLED)
                self.text[username].grid_remove()
                self.online_list_box.insert(END, username)

    def set_msg_show_format(self, username, content):  # 将接受到的消息显示在消息文本框中
        """
        将接受到的消息显示在消息文本框中，自己的消息用蓝色的，别人的消息用黑色
        """
        print(username)
        if username == self.username:
            self.text[self.current_friend].config(state=NORMAL)
            title = username + " " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n"
            self.text[self.current_friend].insert(END, title, "blue")
            self.text[self.current_friend].insert(END, content + "\n", "blue")
            self.text[self.current_friend].see(END)
            self.text[self.current_friend].config(state=DISABLED)
        else:
            self.text[username].config(state=NORMAL)
            title = username + " " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n"
            self.text[username].insert(END, title, "black")
            self.text[username].insert(END, content + "\n")
            self.text[username].see(END)
            self.text[username].config(state=DISABLED)

    def clear_input_box(self):  # 清空消息输入框
        self.input_box.delete('0.0', END)

    def send_func(self):  # 封装到主界面中的发送消息按钮中
        content = self.input_box.get('0.0', END)
        self.set_msg_show_format(self.username,content)
        #信息发送格式：from_user#!to_user#!message
        content=self.username + "#!#!"+ self.current_friend +"#!#!"+ content
        print('send_message content:',content)
        self.client.send_msg(content)
        self.clear_input_box()

    def close_callback(self):  # 关闭socket
        self.client.close()
        self.close()


if __name__ == "__main__":

    mp.freeze_support()
    login_panel = LoginPanel()
    login_panel.show()

